/*****************************************************************************
Copyright 2006 Kervin L. Pierre
    EMAIL: otlkcon@adevsoft.com

This file is part of Otlkcon.

Otlkcon is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

Otlkcon is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Lesser GNU General Public License for more details.

You should have received a copy of the Lesser GNU General Public License
along with Otlkcon; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*******************************************************************************/

#include "stdafx.h"
#include "ochelper.h"
#include "dll_utility.h"

#include <string>
#include <shlwapi.h>

using namespace std;

int otlkcon_debugLevel          
        = OTLKCON_DEFAULT_DEBUGLEVEL;
int otlkcon_logToFileEnabled = 0;
wchar_t otlkcon_logFile[MAX_PATH+1]        = {0};

/*

HRESULT caldav_test()
{
    HRESULT result = S_OK;
    char *uri = NULL;
    char *user = NULL;
    char *pass = NULL;
    LPMAPISESSION sess;
    HKEY hk;
    CaldavConnection *cr = new CaldavConnection();
    
    result = MAPIInitialize(NULL);

    if( result != S_OK )
        return result;

    result = MAPILogonEx(NULL,NULL,NULL,MAPI_USE_DEFAULT ,&sess);

    DWORD szBufSize = 514;
    LPBYTE szBuf = (LPBYTE)calloc(1, 512);
    LONG readRetVal;

    // Get registry settings 
    result = RegOpenKeyEx(HKEY_CURRENT_USER,
        "Software\\Otlkcon - MAPI Message Service\\account1",
		0, KEY_QUERY_VALUE, &hk);
    if( hk != 0 )
    {

	    readRetVal = RegQueryValueEx(hk, "uri", NULL, NULL, (LPBYTE)szBuf, &szBufSize);
        uri = strdup((char *)szBuf);

        readRetVal = RegQueryValueEx(hk, "user", NULL, NULL, (LPBYTE)szBuf, &szBufSize);
        user = strdup((char *)szBuf);

        readRetVal = RegQueryValueEx(hk, "pass", NULL, NULL, (LPBYTE)szBuf, &szBufSize);
        pass = strdup((char *)szBuf);
    }
    RegCloseKey(hk);

    struct icaltimetype tm = icaltime_current_time_with_zone( NULL );

    // Put an event
    icalcomponent *event;
    icalcomponent *vcal;
    icalproperty  *prop;
    icalparameter *param;
    struct icaltimetype atm = tm;

    vcal  = icalcomponent_new_vcalendar();

    prop  = icalproperty_new_version("2.0");
    icalcomponent_add_property(vcal,prop);

    prop  = icalproperty_new_prodid("-//OpenConnector.Org//CalDAV Client//EN");
    icalcomponent_add_property(vcal,prop);

    event = icalcomponent_new_vevent();
    icalcomponent_add_component(vcal, event);
    
    char str[63] = {0};

    result = cr->Init();

    _snprintf((char *)&str, 62,
        "%0.2X%0.2X%0.2X%0.2X-%0.2X%0.2X-%0.2X%0.2X-%0.2X%0.2X-%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X",
        rand()%256, rand()%256, rand()%256, rand()%256, rand()%256,
        rand()%256, rand()%256, rand()%256, rand()%256, rand()%256,
        rand()%256, rand()%256, rand()%256, rand()%256, rand()%256,
        rand()%256);

    icalcomponent_set_uid(event,str);
    icalcomponent_set_dtstart(event,atm);
    icalcomponent_set_dtend(event,atm);
    icalcomponent_set_summary(event,"Test Event");

    prop = icalproperty_new_organizer( cr->user.c_str() );
    param = icalparameter_new_role( ICAL_ROLE_CHAIR );
    icalproperty_add_parameter(prop, param);
    icalcomponent_add_property(event,prop);

    OutputDebugString( icalcomponent_as_ical_string(vcal) );
  
    result = cr->Logon( uri, user, pass );
//    result = cr->PutNewComponent( vcal);

    // Poll time plus a minute
    tm.second -= cr->pollSecs+60;
    tm = icaltime_normalize( tm );

    tm = icaltime_current_time_with_zone( NULL );
    struct icaltimetype tm_end = tm;
    tm.year -= 2;
    tm_end.year += 2;
    tm = icaltime_normalize( tm );
    tm_end = icaltime_normalize( tm_end );

    list<CaldavComponent *> *compsList = new list<CaldavComponent *>;
    result = cr->GetEventsByRange( tm, tm_end, compsList );

    list<string> names;
    names.push_front("3185B18C-C5D0-BDCA-77F6-C22D11442051.ics");
    names.push_front("06AC955B-FFFD-5801-3A83-F169F963CED2.ics");
    names.push_front("does_not_exist.ics");

    compsList->clear();
    result = cr->GetEventsByName( names, compsList);

    map<string,string> *namePairs = new map<string,string>;
    result = cr->GetAllEventNames( namePairs );

    return result;
}

*/

#include "protocols/ical/libical/ical.h"

int main(int argc, char* argv[])
{
	int result = 0;

    LPWSTR *args = CommandLineToArgvW( GetCommandLineW(), &result);

    int find_flag               = 0;
    int mapisvcinf_flag         = 0;
    int show_library_flag       = 0;
    int merge_mapisvcinf_flag   = 0;
    int logfile_flag            = 0;
    int help_flag               = 0;

    wstring find_flag_arg               = L"";
    wstring mapisvcinf_flag_arg         = L"";
    wstring merge_mapisvcinf_flag_arg   = L"";
    wstring show_library_flag_arg       = L"";
    wstring logfile_flag_arg            = L"";
    wstring *prev_flag                  = NULL;

    for(int i=1; i<result; i++)
    {
        if( wcscmp(args[i], L"--find") == 0 )
        {
            find_flag = 1;
            prev_flag = &find_flag_arg;
        }
        else if( wcscmp(args[i], L"--mapisvcinf") == 0 )
        {
            mapisvcinf_flag = 1;
            prev_flag = &mapisvcinf_flag_arg;
        }
        else if( wcscmp(args[i], L"--logfile") == 0 )
        {
            logfile_flag = 1;
            prev_flag = &logfile_flag_arg;
        }
        else if( wcscmp(args[i], L"--merge-mapisvcinf") == 0 )
        {
            merge_mapisvcinf_flag = 1;
            prev_flag = &merge_mapisvcinf_flag_arg;
        }
        else if( wcscmp(args[i], L"--show-library") == 0 )
        {
            show_library_flag = 1;
            prev_flag = &show_library_flag_arg;
        }
        else if( wcscmp(args[i], L"--help") == 0 )
        {
            help_flag = 1;
        }
        else
        {
            if( prev_flag == NULL )
            {
                print_options(1, wstring(L"ERROR: unknown command argument.") );
            }
            prev_flag->assign( (wchar_t *)(args[i]) );
            prev_flag = 0;
        }
    }

    // Do actions.

//    result = caldav_test();

    
    // Setup the log file first if needed
    if( logfile_flag == 1 )
    {
        // log to a file
        wcscpy_s(otlkcon_logFile,MAX_PATH+1,
                        logfile_flag_arg.c_str());
        otlkcon_logToFileEnabled = 1;
    }

    if(help_flag == 1 )
    {
        print_options(0, wstring(L"") );
    }

    wstring found_mapisvcinf_file = L"";
    if( find_flag == 1 )
    {
        if( find_flag_arg.length() == 0 )
        {
            print_options(1, wstring( L"Required arg for --find is missing") );
            result = E_FAIL;
            goto main_end;
        }
        else if( find_flag_arg == L"mapisvcinf" )
        {
            if( find_mapisvc(&found_mapisvcinf_file) == S_OK )
            {
                LOG_PRINTF( OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"SUCCESS: %s\n", found_mapisvcinf_file.c_str());
                mapisvcinf_flag_arg = found_mapisvcinf_file;
            }
            else
            {
                // Error condition
                goto main_end;
            }
        }
        else print_options(1, wstring( L"ERROR: Unknown option for --find flag.") );
    }

    if( show_library_flag == 1 )
    {
        result = otlkcon_loadlibrary( L"" );
        if( result != S_OK )
        {
            LOG_PRINTF( OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"ERROR: Could not load the 'mstore32.dll' library\n" );
        }
    }

    if( mapisvcinf_flag == 1 )
    {
        if( mapisvcinf_flag_arg.length() == 0 )
            print_options(1, wstring( L"Required arg for --mapisvcinf is missing"));
    }

    if( merge_mapisvcinf_flag == 1 )
    {
        if( merge_mapisvcinf_flag_arg.length() == 0 )
        {
            print_options(1, wstring( L"Required arg for --merge-mapisvcinf is missing"));
        }

        // TODO: Save file in the format + "20060109-01.bak"

        // TODO: If the backup destination exists we should simply
        //       Try with another. eg "20060109-02.bak"
        wstring bkFile = mapisvcinf_flag_arg + L".bak";
        if( mapisvcinf_flag_arg.length() > 0 )
        {
            result = merge_mapisvcinf(merge_mapisvcinf_flag_arg,
                                            mapisvcinf_flag_arg, bkFile);
        }
        else print_options(1, 
            wstring( L"No 'MapiSvc.inf' given.  Use either '--find mapisvcinf' or '--mapisvcinf <filepath>'") );
    }

main_end:
	return result;
}

void print_options(int retVal, wstring str)
{
    LOG_PRINTF( OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL|OTLKCON_LOG_FLAG_NO_DATE, L"ochelper - Do stuff for otlkcon\n" );
    LOG_PRINTF( OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL|OTLKCON_LOG_FLAG_NO_DATE, L"%s\n\n", str.c_str() );
    LOG_PRINTF( OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL|OTLKCON_LOG_FLAG_NO_DATE, L"ochelper.exe [--find mapisvcinf] [--mapisvcinf filename] [--merge-mapisvcinf filename]\n\n" );
    LOG_PRINTF( OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL|OTLKCON_LOG_FLAG_NO_DATE, L"--find - Find a configuration file. Currently only mapisvc.inf is supported.\n" );
    LOG_PRINTF( OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL|OTLKCON_LOG_FLAG_NO_DATE, L"--mapisvcinf - Specify the full mapisvc.inf for an operation\n" );
    LOG_PRINTF( OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL|OTLKCON_LOG_FLAG_NO_DATE, L"--merge-mapisvcinf - Merge an INI file with MapiSvc.inf\n" );

    exit(retVal);
}

//Finds the MAPSVC.INF file
// MSDN KB Article Q229700 Discusses this
HRESULT find_mapisvc(wstring *file)
{
    HRESULT result       = 0;
    DWORD szBufSize    = 0;
    wchar_t compID[OUTLOOK_REGKEY_MAX_LEN+1] = {0};
    wchar_t appID[OUTLOOK_REGKEY_MAX_LEN+1]  = {0};
    wchar_t *sbFile = NULL;
    HKEY hk;
    LPFGETCOMPONENTPATH pfnFGetComponentPath;
    HINSTANCE hinstStub = NULL;
    static const basic_string <char>::size_type npos = -1;

    //Get the compID and appID
    if( RegOpenKeyExW(HKEY_LOCAL_MACHINE, OUTLOOK_KEY,
		0, KEY_QUERY_VALUE, &hk) != ERROR_SUCCESS )
	{
        LOG_PRINTF( OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"Error: Could not open '%s' registry key\n" , OUTLOOK_KEY);
        return E_FAIL; 
	}
    szBufSize = OUTLOOK_REGKEY_MAX_LEN;
    result = RegQueryValueExW(hk, OUTLOOK_COMP_ID, NULL, NULL,
                (LPBYTE)&compID, &szBufSize);
    if( result != ERROR_SUCCESS )
	{
        LOG_PRINTF( OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"Error: Could not retrieve CompID from registy\n" );
        return E_FAIL; 
	}
    szBufSize = OUTLOOK_REGKEY_MAX_LEN;
    result = RegQueryValueExW(hk, OUTLOOK_APP_ID, NULL, NULL,
                (LPBYTE)&appID, &szBufSize);
    if( result != ERROR_SUCCESS )
	{
        LOG_PRINTF( OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"Error: Could not retrieve AppID from registy\n" );
        return E_FAIL; 
	}
    RegCloseKey(hk);

    //This the backward compatible way first...
    hinstStub = LoadLibraryW( L"mapistub.dll");
	if (!hinstStub)
		hinstStub = LoadLibraryW( L"mapi32.dll");

    if(hinstStub)
	{
		pfnFGetComponentPath 
            = (LPFGETCOMPONENTPATH)GetProcAddress(hinstStub, "FGetComponentPath");

        char *aCompID = (char *)calloc(1, wcslen(compID) + 1);
        sprintf_s( aCompID, wcslen(compID)+1, "%S", compID );
        char *aAppID  = (char *)calloc(1, wcslen(appID) + 1);
        sprintf_s( aAppID, wcslen(appID)+1, "%S", appID );
        char *aSbFile = (char *)calloc(1, MAX_PATH + 1);

		if (pfnFGetComponentPath 
                    && pfnFGetComponentPath(aCompID, NULL, aSbFile, MAX_PATH, TRUE) )
        {
            //We should now have the DLL Path, replace the filename
            //then return
            sbFile = (wchar_t *)calloc(1, (strlen(aSbFile)+1)*sizeof(wchar_t) );
            swprintf_s(sbFile, strlen(aSbFile)+1, L"%S", aSbFile );
            file->assign( sbFile );
            basic_string <wchar_t>::size_type i = file->rfind('\\');
            free(sbFile);

            if( i  !=  npos )
            {
                file->assign( file->substr( 0, i+1 ) + OUTLOOK_MAPISVCINF );
                return S_OK; 
            }
        }
        
        free(aSbFile);
        free(aAppID);
        free(aCompID);
    }

    //Now try the System folder
    file->assign(L"");
    wchar_t wbuf[MAX_PATH] = {0};
	if( (result = GetSystemDirectoryW((LPWSTR)&wbuf, MAX_PATH)) == 0 )
    {
        LOG_PRINTF( OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"Error: Could not retrieve system folder\n" );
		return E_FAIL;
    }
    file->append( wbuf );
    file->append( L"\\" );
    file->append( OUTLOOK_MAPISVCINF );

    return S_OK;
}

// Given the full path to MAPISvc.INF and a INI file fragment,
// merge the two files into MAPISvc.INF after backing up the
// old file.
HRESULT merge_mapisvcinf( wstring inifile, 
                          wstring mapisvcinf, 
                          wstring mapisvcinf_bak )
{
	LPWSTR szSectionBuff, szBuff, szBuff2, szValueBuff; 
	ULONG szBuffSize;
    wstring valueTemp;
    basic_string <wchar_t>::size_type i;
    static const basic_string <wchar_t>::size_type npos = -1;
	HRESULT result = S_OK;
 
    // One 2MB
    szBuffSize = 1024 * 1024;

    LOG_PRINTF( OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
        L" Merging '%s' with '%s'", inifile.c_str(), mapisvcinf.c_str() );

	if ((CopyFileW(mapisvcinf.c_str(),mapisvcinf_bak.c_str(),false))==false)
    {
        //TODO:  We should return specific error codes so the gui can relay
        //       The problem to the user.

        //copy attempt failed
        LOG_PRINTF( OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"ochelper mergeini aborted- mapisvcinf file backup failed " );

        return E_FAIL;
	}

    if( PathFileExistsW( inifile.c_str() ) == FALSE )
    {
        LOG_PRINTF( OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"ochelper mergeini aborted- '%s' not found ", inifile.c_str() );

        return E_FAIL;
	}

    szBuff = (wchar_t *)calloc(szBuffSize, sizeof(wchar_t));
    szSectionBuff = szBuff;
    szBuff2 = (wchar_t *)calloc(szBuffSize, sizeof(wchar_t));
    szValueBuff = szBuff2;

    // Get sectionnames from API and split it into array of strings
    result = GetPrivateProfileSectionNamesW( szSectionBuff,
                                            szBuffSize, inifile.c_str() );
    if( result == (szBuffSize - 2) )
    {
        // TODO: The buffer was too small.  Handle it.
        DebugBreak();

        return E_FAIL;
    }

    //for each element in section name....go get stuff for section 
    while( wcslen( szSectionBuff ) )
    {
        result = GetPrivateProfileSectionW( szSectionBuff, szValueBuff,
                                            szBuffSize, inifile.c_str() );
        if( result == ( szBuffSize - 2 ) )
        {
            // TODO: Buff too small. Handle it.
            DebugBreak();

            return E_FAIL;
        }
 
        while( wcslen( szValueBuff ) )
        {
            valueTemp = szValueBuff;
            i = valueTemp.find( L"=" );
            if( i == npos )
            {
                // TODO: '=' not found.  Handle it.
                DebugBreak();
            }
            else
            {
                result = WritePrivateProfileStringW( szSectionBuff, 
                    valueTemp.substr(0,i).c_str(),
                    valueTemp.substr(i+1).c_str(),
                    mapisvcinf.c_str() );
            }

            szValueBuff += wcslen( szValueBuff ) + 1;
        }

        memset( szBuff2, szBuffSize, sizeof(wchar_t) );
        szValueBuff = szBuff2;

        // Get the next section name
        szSectionBuff += wcslen( szSectionBuff ) + 1;
    }
    free( szBuff );
    free( szBuff2 );

    return S_OK;
}